package com.free4inno.knowledgems.utils;

import com.free4inno.knowledgems.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Author: HaoYi.
 * Date: 2020/10/22.
 */

@Slf4j
@Component
public class ImageUtils {

    // nfs地址是否被使用
    @Value("${file.nfs.used:false}")
    private Boolean nfsUsed;

    // 获取NFS的url
    @Value("${file.nfs.url:}")
    private String nfsUrl;

    // file save path
    @Value("${file.save.path}")
    private String fileSavePath;

    // file upload environment
    @Value("${file.environment}")
    private String fileEnvironment;

    @Autowired
    private FileService fileService;

    //获取图片url
    public List<String> getImgSrc(String text) {
        log.info(LogUtils.parse("获取图片Url", "在资源正文html中获取图片Url"));
        List<String> list = new ArrayList<String>();
        Pattern pImg = Pattern.compile("<(img|IMG)(.*?)(/>|></img>|>)");
        Matcher mImg = pImg.matcher(text);
        boolean resultImg = mImg.find();
        if (resultImg) {
            while (resultImg) {
                //获取到匹配的<img />标签中的内容
                String strImg = mImg.group(2);

                //开始匹配<img />标签中的src
                Pattern pSrc = Pattern.compile("(src|SRC)=(\"|\')(.*?)(\"|\')");
                Matcher mSrc = pSrc.matcher(strImg);
                if (mSrc.find()) {
                    String strSrc = mSrc.group(3);
                    list.add(strSrc);
                    //结束匹配<img />标签中的src

                    //匹配content中是否存在下一个<img />标签，有则继续以上步骤匹配<img />标签中的src
                    resultImg = mImg.find();
                }
            }
        }
        return list;
    }

    //下载图片到服务器
    public String[] download(String urlString) throws Exception {
        String[] resultArray = {"", ""};
        //判断是否为外链图片
        if (urlString.startsWith("http") && !urlString.contains(nfsUrl)) {
            String[] picNameArray = {"", ""};
            log.info(LogUtils.parse("下载外链图片", "下载外链图片到服务器，url:" + urlString));
            urlString = urlString.replace("&amp;", "&");
            // 文件名称
            picNameArray = getPicName(urlString);
            String oldFilename = picNameArray[1];
            if (oldFilename.equals("error")) {
                log.error(LogUtils.parse("下载外链图片", "下载外链图片失败！url:" + urlString));
                resultArray[0] = picNameArray[0];
                resultArray[1] = urlString;
                return resultArray;
            } else {
                InputStream is = null;
                OutputStream os = null;
                try {
                    // 构造URL
                    URL url = new URL(urlString);
                    // 1M的数据缓冲
                    byte[] bs = new byte[1024 * 1024];
                    // 打开连接
                    HttpURLConnection conPic = (HttpURLConnection) url.openConnection();
                    //**********实际设置请求超时为5s**********
                    conPic.setConnectTimeout(5000);
                    conPic.setReadTimeout(5000);
                    // 重启输入流
                    is = new DataInputStream(conPic.getInputStream());
                    // 读取到的数据长度
                    int tot = 0;
                    int len = 0;
                    //获取文件夹路径
                    String savePath = fileService.getSavePath();
                    String fileFolder = fileService.getUploadFileFolder(oldFilename);
                    String fileName = fileService.getUploadFileName(oldFilename);
                    String direPath = savePath + fileFolder + "/";
                    File direFile = new File(direPath);
                    //文件夹如果不存在，新建文件夹
                    if (!direFile.exists() || !direFile.isDirectory()) {
                        direFile.mkdirs();
                    }
                    os = new DataOutputStream(new FileOutputStream(direFile.getPath() + "/" + fileName));
                    // 开始读取
                    long beginT = System.currentTimeMillis();
                    while ((len = is.read(bs)) != -1) {
                        tot += len;
                        os.write(bs, 0, len);
                    }
                    long endT = System.currentTimeMillis();
                    os.flush();
                    log.info(LogUtils.parse("下载外链图片", "下载外链图片成功"));
                    // 完毕，关闭所有链接
                    os.close();
                    is.close();
                    // 返回
                    String lastUrl = fileFolder + "/" + fileName;
                    String location = "/util/downloadImage?id=" + lastUrl.replaceAll("\\\\", "/"); //反斜杠\转成斜杠/
                    resultArray[0] = picNameArray[0];
                    resultArray[1] = location;
                    return resultArray;
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error(LogUtils.parse("下载外链图片", "下载外链图片失败！url:" + urlString));
                    resultArray[0] = "error";
                    resultArray[1] = urlString;
                    return resultArray;
                } finally {
                    try {
                        if (is != null) {
                            is.close();
                        }
                        if (os != null) {
                            os.close();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error(LogUtils.parse("下载外链图片", "关闭输入输出流错误！"));
                    }
                }
            }
        } else {
            resultArray[0] = "local";
            resultArray[1] = "图片非外链图片：" + urlString;
            return resultArray;
        }
    }

    //通过url生成文件流并读取图片格式
    public String[] getPicName(String urlString) throws Exception {
        String[] array = {"", ""};
        try {
            // 构造URL
            URL url = new URL(urlString);
            //生成uuid作为文件名称
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            // 打开连接
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            //设置请求超时为5s
            con.setConnectTimeout(5000);
            con.setReadTimeout(5000);
            // 获取HTTP状态码
            int httpStatusCode = con.getResponseCode();
            // 输入流获取文件名称
            InputStream fis = new DataInputStream(con.getInputStream());
            String prefix = getPicType(fis);
            // PicName
            String filename = "";
            if (prefix.equals(null) || !(prefix.substring(0, 1).equals("."))) {
                filename = "error";
            } else {
                filename = uuid + prefix;
            }
            // 关闭连接
            fis.close();
            array[0] = Integer.toString(httpStatusCode);
            array[1] = filename;
            return array;
        } catch (Exception e) {
            e.printStackTrace();
            String filename = "error";
            array[0] = "error";
            array[1] = filename;
            return array;
        }
    }

    //    常用文件的文件头如下：(以前六位为准)
//    JPEG (jpg)，文件头：FFD8FF
//    PNG (png)，文件头：89504E47
//    GIF (gif)，文件头：47494638
//    TIFF (tif)，文件头：49492A00
//    Windows Bitmap (bmp)，文件头：424D
//    CAD (dwg)，文件头：41433130
//    Adobe Photoshop (psd)，文件头：38425053
//    Rich Text Format (rtf)，文件头：7B5C727466
//    XML (xml)，文件头：3C3F786D6C
//    HTML (html)，文件头：68746D6C3E
//    Email [thorough only] (eml)，文件头：44656C69766572792D646174653A
//    Outlook Express (dbx)，文件头：CFAD12FEC5FD746F
//    Outlook (pst)，文件头：2142444E
//    MS Word/Excel (xls.or.doc)，文件头：D0CF11E0
//    MS Access (mdb)，文件头：5374616E64617264204A
//    WordPerfect (wpd)，文件头：FF575043
//    Postscript (eps.or.ps)，文件头：252150532D41646F6265
//    Adobe Acrobat (pdf)，文件头：255044462D312E
//    Quicken (qdf)，文件头：AC9EBD8F
//    Windows Password (pwl)，文件头：E3828596
//    ZIP Archive (zip)，文件头：504B0304
//    RAR Archive (rar)，文件头：52617221
//    Wave (wav)，文件头：57415645
//    AVI (avi)，文件头：41564920
//    Real Audio (ram)，文件头：2E7261FD
//    Real Media (rm)，文件头：2E524D46
//    MPEG (mpg)，文件头：000001BA
//    MPEG (mpg)，文件头：000001B3
//    Quicktime (mov)，文件头：6D6F6F76
//    Windows Media (asf)，文件头：3026B2758E66CF11
//    MIDI (mid)，文件头：4D546864
    public static final String TYPE_JPG = ".jpg";
    public static final String TYPE_GIF = ".gif";
    public static final String TYPE_PNG = ".png";
    public static final String TYPE_BMP = ".bmp";
    public static final String TYPE_WEBP = ".webp";
    public static final String TYPE_TIF = ".tif";

    /**
     * byte数组转换成16进制字符串.
     *
     * @param src
     * @return String
     */
    public String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }


    /**
     * 根据文件流判断图片类型.
     *
     * @param fis
     * @return jpg/png/gif/bmp/webp/tif
     */
    public String getPicType(InputStream fis) {
        //读取文件的前几个字节来判断图片格式
        byte[] b = new byte[4];
        try {
            fis.read(b, 0, b.length);
            String type = bytesToHexString(b).toUpperCase();
            if (type.contains("FFD8FF")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为JPG"));
                return TYPE_JPG;
            } else if (type.contains("89504E")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为PNG"));
                return TYPE_PNG;
            } else if (type.contains("474946")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为GIF"));
                return TYPE_GIF;
            } else if (type.contains("424D")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为BMP"));
                return TYPE_BMP;
            } else if (type.contains("524946")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为WEBP"));
                return TYPE_WEBP;
            } else if (type.contains("49492A")) {
                log.info(LogUtils.parse("解析图片格式", "返回，判断为TIF"));
                return TYPE_TIF;
            } else {
                log.info(LogUtils.parse("解析图片格式", "未判断出类型！返回null"));
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.warn(LogUtils.parse("解析图片格式", "异常！返回null"));
        return null;
    }

    /**
     * 删除服务器中图片.
     *
     * @param saveUrl
     * @return String
     */
    public String deleteImage(String saveUrl) {
        String result = "";
        if (fileService.deleteFile(saveUrl)) {
            result = "SUCCESS_DELETE(" + saveUrl + ")";
            log.info(LogUtils.parse("删除图片", "删除成功, url:" + saveUrl));
        } else {
            result = "ERROR_DELETE(" + saveUrl + ")";
            log.error(LogUtils.parse("删除图片", "删除失败！url:" + saveUrl));
        }
        return result;
    }

    //自动建立uploadfiles系列文件夹
    public String checkFolders() {
        //当前运行路径
        String runPath = System.getProperty("user.dir");
        //uploadfiles文件夹路径
        String uploadfilesUrl = runPath + fileSavePath;
        File direFile = new File(uploadfilesUrl);
        String result = "";
        //文件夹如果不存在，新建文件夹
        if (!direFile.exists() || !direFile.isDirectory()) {
            direFile.mkdirs();
        }
        //内部文件夹
        String[] folderNames = {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F"};
        List<String> folderNamesList = Arrays.asList(folderNames);
        for (String folderName : folderNamesList) {
            String folderUrl = uploadfilesUrl + folderName + "/";
            File folderFile = new File(folderUrl);
            //文件夹如果不存在，新建文件夹
            if (!folderFile.exists() || !folderFile.isDirectory()) {
                folderFile.mkdirs();
                result = "uploadfiles系列文件夹已建好\n";
            } else {
                result = "\nuploadfiles系列文件夹存在\n";
            }
        }
        return result;
    }

    public String DownloadExternalImage(String text, HttpSession session){
        List<String> imageSrc = getImgSrc(text);
        //计算程序运行时长的线程池
        final ExecutorService exec = Executors.newFixedThreadPool(1);
        //下载图片，并把img src替换为本地路径
        for (String src : imageSrc) {
            Callable<String[]> call = new Callable<String[]>() {
                public String[] call() throws Exception {
                    //开始执行下载图片操作
                    //**********重要！！download函数内部下载时有一个5s下载时长限制，如若调整下载等待时间，一定要调函数内部**********
                    String[] resultArray = download(src);
                    //**********重要！！download函数内部下载时有一个5s下载时长限制，如若调整下载等待时间，一定要调函数内部**********
                    return resultArray;
                }
            };
            try {
                //控制程序运行时间
                Future<String[]> future = exec.submit(call);
                //**********重要！！下载图片函数超时时间设为 6 秒，超时则报异常**********
                String[] resultArray = future.get(1000 * 6, TimeUnit.MILLISECONDS);
                if (resultArray[0].substring(0, 1).equals("2")) {
                    String newsrc = resultArray[1];
                    text = text.replace(src, newsrc);
                } else if (resultArray[0].substring(0, 1).equals("l")) {
                } else {
                    break;
                }
            } catch (TimeoutException e) {
                e.printStackTrace();
                break; //下载出问题则不再下载图片，直接返回
            } catch (Exception e) {
                e.printStackTrace();
                break; //下载出问题则不再下载图片，直接返回
            }
        }
        // 关闭线程池
        exec.shutdown();
        return text;
    }
}
